using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using RaisingStudio.Common.Exceptions;
using System.Collections.Specialized;
using RaisingStudio.Common.Xml;
using RaisingStudio.Data.Common.Configuration;
using System.Data;

namespace RaisingStudio.Data.Common.Managers
{
    public sealed class ProviderManager
    {
        #region Instance
        //public static readonly ProviderManager Instance = new ProviderManager();
        private static volatile ProviderManager _instance = null;
        public static ProviderManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (typeof(ProviderManager))
                    {
                        if (_instance == null)
                        {
                            _instance = new ProviderManager();
                        }
                    }
                }
                return _instance;
            }
        }
        #endregion
        
        /// <summary>
        /// Token for default providers config file name.
        /// </summary>
        private const string PROVIDERS_FILE_NAME = "providers.config";

        /// <summary>
        /// Token for xml path to provider elements.
        /// </summary>
        private const string XML_PROVIDER = "providers/provider";
        
        /// <summary>
        /// Default provider name
        /// </summary>
        private const string DEFAULT_PROVIDER_NAME = "_DEFAULT_PROVIDER_NAME";

        private HybridDictionary _providers = new HybridDictionary();
        ///// <summary>
        /////  List of providers
        ///// </summary>
        //public HybridDictionary Providers
        //{
        //    get { return _providers; }
        //}

        private ProviderManager()
        {
            _providers.Clear();
            GetProviders();
        }

        /// <summary>
        /// Load and initialize providers from specified file.
        /// </summary>
        private void GetProviders()
        {
            IDbProvider provider;
            XmlDocument xmlProviders = Resources.GetConfigAsXmlDocument(PROVIDERS_FILE_NAME);
            lock (this.providers_lock)
            {
                provider = GetProviders(xmlProviders);
            }
        }

        private object providers_lock = new object();

        public void AppendProviders(string baseDirectory)
        {
            IDbProvider provider;
            XmlDocument xmlProviders = Resources.GetConfigAsXmlDocument(baseDirectory, PROVIDERS_FILE_NAME);
            lock (this.providers_lock)
            {
                provider = GetProviders(xmlProviders);
            }
        }

        private IDbProvider GetProviders(XmlDocument xmlProviders)
        {
            IDbProvider provider = null;
            if ((xmlProviders != null) && (xmlProviders.DocumentElement != null))
            {
                if (!string.IsNullOrEmpty(xmlProviders.DocumentElement.NamespaceURI))
                {
                    XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(xmlProviders.NameTable);
                    xmlNamespaceManager.AddNamespace(PROVIDERS_NAMESPACE_PREFIX, xmlProviders.DocumentElement.NamespaceURI);
                    foreach (XmlNode node in xmlProviders.SelectNodes(ApplyProviderNamespacePrefix(XML_PROVIDER), xmlNamespaceManager))
                    {
                        provider = ProcessProviderNode(node);
                    }
                }
                else
                {
                    foreach (XmlNode node in xmlProviders.SelectNodes(XML_PROVIDER))
                    {
                        provider = ProcessProviderNode(node);
                    }
                }
            }
            return provider;
        }

        private IDbProvider ProcessProviderNode(XmlNode node)
        {
            IDbProvider provider;
            provider = ProviderDeSerializer.Deserialize(node);

            if (provider.IsEnabled)
            {
                provider.Initialize();
                this._providers.Add(provider.Name, provider);

                if (provider.IsDefault)
                {
                    if (this._providers[DEFAULT_PROVIDER_NAME] == null)
                    {
                        this._providers.Add(DEFAULT_PROVIDER_NAME, provider);
                    }
                    else
                    {
                        throw new ConfigurationException(
                            string.Format("Error while configuring the Provider named \"{0}\" There can be only one default Provider.", provider.Name));
                    }
                }
            }
            return provider;
        }

        private const string PROVIDERS_NAMESPACE_PREFIX = "provider";

        /// <summary>
        /// Apply the provider namespace prefix
        /// </summary>
        /// <param name="elementName"></param>
        /// <returns></returns>
        public string ApplyProviderNamespacePrefix(string elementName)
        {
            return PROVIDERS_NAMESPACE_PREFIX + ":" + elementName.
                Replace("/", "/" + PROVIDERS_NAMESPACE_PREFIX + ":");
        }

        public DbProvider GetProvider(string providerName)
        {
            if (!string.IsNullOrEmpty(providerName))
            {
                lock (this.providers_lock)
                {
                    return this._providers[providerName] as DbProvider;
                }
            }
            return null;
        }
        
        public bool ProviderExists(string providerName)
        {
            if (!string.IsNullOrEmpty(providerName))
            {
                lock (this.providers_lock)
                {
                    return (this._providers[providerName] != null);
                }
            }
            else
            {
                return false;
            }
        }
        
        public System.Data.IDbConnection CreateConnection(string providerName, string connectionString)
        {
            DbProvider dbProvider = GetProvider(providerName);
            if (dbProvider != null)
            {
                IDbConnection idbConnection = dbProvider.CreateConnection();
                System.Data.IDbConnection connection = idbConnection as System.Data.IDbConnection;
                if (connection != null)
                {
                    connection.ConnectionString = connectionString;
                    return connection;
                }
            }
            return null;
        }

        public System.Data.IDbDataAdapter CreateDataAdapter(string providerName)
        {
            DbProvider dbProvider = GetProvider(providerName);
            if (dbProvider != null)
            {
                IDbDataAdapter idbDataAdapter = dbProvider.CreateDataAdapter();
                System.Data.IDbDataAdapter dataAdapter = idbDataAdapter as System.Data.IDbDataAdapter;
                return dataAdapter;
            }
            return null;
        }

        public object CreateCommandBuilder(string providerName, System.Data.IDbDataAdapter dataAdapter)
        {
            DbProvider dbProvider = GetProvider(providerName);
            if (dbProvider != null)
            {
                object commandBuilder = dbProvider.CreateCommandBuilder(dataAdapter);
                return commandBuilder;
            }
            return null;
        }

        public const string POSITIONALPARAMETER = "?";

        public static string TrimStringStart(string text, string trimText)
        {
            if (text.StartsWith(trimText))
            {
                return text.Remove(0, trimText.Length);
            }
            return text;
        }

        public string GetParameterName(string providerName, string commonParameterName, string commonParameterPrefix, out string parameterNameInCommandText)
        {
            string parameterName = commonParameterName;
            parameterNameInCommandText = commonParameterName;
            // TODO:
            DbProvider dbProvider = GetProvider(providerName);
            if (dbProvider != null)
            {
                // rule: UsePositionalParameters,
                if (dbProvider.UsePositionalParameters)
                {
                    parameterName = POSITIONALPARAMETER;
                    parameterNameInCommandText = POSITIONALPARAMETER;                    
                }
                else
                {
                    if (dbProvider.UseParameterPrefixInParameter)
                    {
                        if (dbProvider.ParameterPrefix != commonParameterPrefix)
                        {
                            parameterName = string.Format("{0}{1}", dbProvider.ParameterPrefix, TrimStringStart(commonParameterName, commonParameterPrefix));
                        }
                    }
                    else
                    {
                        parameterName = TrimStringStart(commonParameterName, commonParameterPrefix);
                    }
                    if (dbProvider.UseParameterPrefixInSql)
                    {
                        if (dbProvider.ParameterPrefix != commonParameterPrefix)
                        {
                            parameterNameInCommandText = string.Format("{0}{1}", dbProvider.ParameterPrefix, TrimStringStart(commonParameterName, commonParameterPrefix));
                        }
                    }
                    else
                    {
                        parameterNameInCommandText = TrimStringStart(commonParameterName, commonParameterPrefix);
                    }
                }
                return parameterName;
            }
            else
            {
                // TODO: exception.
            }
            return null;
        }
    }
}
